package com.ruoyi.common.utils.file;

import java.io.File;
import java.io.IOException;

import com.ruoyi.common.exception.ResultException;
import net.coobird.thumbnailator.Thumbnails;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.config.Global;
import com.ruoyi.common.exception.file.FileNameLengthLimitExceededException;
import com.ruoyi.common.exception.file.FileSizeLimitExceededException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.security.Md5Utils;

/**
 * 文件上传工具类
 * 
 * @author ruoyi
 */
public class FileUploadUtils
{
    private static final Logger log = LoggerFactory.getLogger(FileUploadUtils.class);
    /**
     * 默认大小 50M
     */
    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;

    /**
     * 默认的文件名最大长度 100
     */
    public static final int DEFAULT_FILE_NAME_LENGTH = 100;

    /**
     * 上传文件大小限制，为0表示不限制
     */
    private static final long MAX_FILE_SIZE = Global.getInt("njbjs.max-file-size", 0) * 1024 * 1024;
    /**
     * 图片压缩后的最大尺寸，为0表示不压缩
     */
    private static final long MAX_IMAGE_THUMB_SIZE = Global.getInt("njbjs.max-image-thumb-size", 0) * 1024;


    /**
     * 默认上传的地址
     */
    private static final String defaultBaseDir = Global.getProfile();

    /**
     * 默认文件类型jpg
     */
    public static final String IMAGE_JPG_EXTENSION = ".jpg";

    private static int counter = 0;


    /**
     * 默认配置的文件上传
     *
     * @param dirPath 相对应用的基目录
     * @param file    上传的文件
     * @return 文件名称
     * @throws IOException 比如读写文件出错时
     */
    public static String uploadFileRename(String dirPath, MultipartFile file) throws ResultException {
        return uploadFileRename(dirPath, file, true);
    }

    /**
     * 根据文件路径上传
     * 此方法直接把源文件上传，不做压缩，只做重命名
     *
     * @param dirPath 相对应用的基目录
     * @param file    上传的文件
     * @param partitionByDate 是否按日期存放
     * @return 文件名称
     * @throws ResultException 如果超出最大大小
     * @throws IOException 比如读写文件出错时
     */
    public static String uploadFileRename(String dirPath, MultipartFile file, boolean partitionByDate) throws
            ResultException {
        //判断文件大小是否超出限制
        assertAllowed(file);

        //文件重命名
        String fileName = encodingFilename(file.getOriginalFilename());

        //文件目录初始化，如果不存在则创建
        String filePath = initFilePath(dirPath, partitionByDate);//文件保存的绝对路径
        String relativePath = filePath.substring(filePath.indexOf("/"));//文件保存的相对路径，如果是widows环境，则去除D:

        //文件转储
        File desc = new File(filePath + fileName);
        try {
            file.transferTo(desc);
        } catch (IOException e) {
            throw new ResultException("文件保存失败");
        }

        //返回文件相对目录，用于展示和保存到数据库
        return relativePath + fileName;
    }

    /**
     * 默认配置的上传图片，重命名且压缩
     *
     * @param dirPath 相对应用的基目录
     * @param file    上传的文件
     * @return 文件名称
     * @throws ResultException 如果超出最大大小
     */
    public static String uploadImageRenameAndComp(String dirPath, MultipartFile file) throws ResultException {
        return uploadImageRenameAndComp(dirPath, file, true);
    }

    /**
     * 上传图片，重命名且压缩
     *
     * @param dirPath 相对应用的基目录
     * @param file    上传的文件
     * @param partitionByDate 是否按日期存放
     * @return 文件名称
     * @throws ResultException 如果超出最大大小
     */
    public static String uploadImageRenameAndComp(String dirPath, MultipartFile file, boolean partitionByDate) throws ResultException{
        //判断文件大小是否超出限制
        assertAllowed(file);

        //判断文件名是否合法
        //获取后缀名
        String originalName = file.getOriginalFilename();
        String suffix = originalName.substring(originalName.lastIndexOf(".") + 1);
        if (!"jpg".equalsIgnoreCase(suffix) && !"jpeg".equalsIgnoreCase(suffix) && !"png".equalsIgnoreCase(suffix)
                && !"gif".equalsIgnoreCase(suffix)&& !"jfif".equalsIgnoreCase(suffix)) {
            throw new ResultException("图片格式不正确");
        }

        //文件重命名
        String fileName = encodingFilename(originalName);

        //文件目录初始化，如果不存在则创建
        String filePath = initFilePath(dirPath, partitionByDate);//文件保存的绝对路径
        String relativePath = filePath.substring(filePath.indexOf("/"));//文件保存的相对路径，如果是widows环境，则去除D:

        //文件转储
        String realFilePath = filePath + fileName;
        File desc = new File(realFilePath);
        try {
            file.transferTo(desc);
        } catch (IOException e) {
            throw new ResultException("文件保存失败");
        }

        //如果是jpg图片或png图片，且图片大小超过要求的尺寸，则进行压缩处理
        if (("jpg".equalsIgnoreCase(suffix) || "jpeg".equalsIgnoreCase(suffix) || "png".equalsIgnoreCase(suffix))
                && MAX_IMAGE_THUMB_SIZE > 0 && file.getSize() > MAX_IMAGE_THUMB_SIZE) {
            //压缩
            String compFileName = "thumb_" + fileName;
            compFileName = compFileName.toLowerCase();
            if (compFileName.contains(".png")) {
                compFileName = compFileName.replace(".png", ".jpg");
            }
            cycleCompress(realFilePath, filePath + compFileName, 1f, MAX_IMAGE_THUMB_SIZE);
            return relativePath + compFileName;
        } else {
            return relativePath + fileName;
        }
    }

    /**
     * 循环压缩，直到合适大小
     * @param srcPath 源文件路径
     * @param destPath 目标文件路径
     * @param quality 压缩质量
     * @param compSize 压缩后的目标文件最大尺寸
     * @throws ResultException 异常结果
     */
    public static void cycleCompress(String srcPath, String destPath, float quality, long compSize) throws ResultException {
        try {
            Thumbnails.of(srcPath).size(4000,4000).outputQuality(quality).toFile(destPath);
        } catch (IOException e) {
            throw new ResultException("图片处理异常");
        }
        File thumbFile = new File(destPath);
        if (thumbFile.length() > compSize) {	// 大于最大值，再次压缩
            thumbFile.delete();	// 删除源文件，quality不会随图片的减小而减小，所以重新压缩改变quality大小即可
            quality = quality - 0.1f;
            if (quality <= 0) {
                return ;
            }
            cycleCompress(srcPath, destPath, quality, compSize);
        }
    }

    /**
     * 初始化文件目录
     * @param dirPath 相对目录
     * @return 已初始化的绝对路径
     * @throws IOException
     */
    private static String initFilePath(String dirPath, boolean partitionByDate) throws ResultException {
        String filePath = defaultBaseDir + dirPath;//文件保存的绝对路径
        if (partitionByDate) {
            filePath += DateUtils.dateTime() + "/";
        }
        //如果文件夹不存在则创建
        File fileAddress = new File(filePath);
        if (!fileAddress.exists()) {
            if (!fileAddress.mkdirs()) {
                log.error("文件目录[{}]创建失败", filePath);
                throw new ResultException("目录创建失败");
            }
        }

        return filePath;
    }

    /**
     * 编码文件名，重命名文件
     */
    public static String encodingFilename(String filename) {
        if (filename == null) {
            filename = "";
        }
        String ext = filename.substring(filename.lastIndexOf(".") + 1);
        filename = filename.replace("_", " ");
        filename = Md5Utils.hash(filename + System.nanoTime() + counter++) + "." + ext;
        return filename;
    }

    /**
     * 文件大小校验
     *
     * @param file 上传的文件
     * @throws ResultException 如果超出最大大小
     */
    private static void assertAllowed(MultipartFile file) throws ResultException {
        if (file == null || file.getOriginalFilename() == null || file.getSize() == 0) {
            throw new ResultException("文件不能为空");
        }
        long size = file.getSize();
        if (MAX_FILE_SIZE != 0 && size > MAX_FILE_SIZE) {
            throw new ResultException("文件太大");
        }
    }
}
